/*
==========================================================
DX490a - Summer 2010
Instructor: Stelios Manousakis
==========================================================
Class 9.1:
Interfacing 5: Arduino in SuperCollider
Contents:
• Connecting (5 steps)
• Activating and setting pins: digital/analog, input/output
- Digital ports
- Digital pins
- Analog ports and pins
• Reading data (inputs)
- Digital read
- Analog read
• Writing data (outputs)
- Digital write
- PWM write
• Disconnecting
• Sound example
==========================================================
*/
// ================= ARDUINO AND SUPERCOLLIDER =================
// The Arduino is a microcontroller development platform; it is an open-source hardware/software system. You can find more info here:
"open http://www.arduino.cc".unixCmd
// Connects via the SerialPort
// Best results using the SCPyduino Class in SC, which is what this file deals with
// Needs Firmata firmaware uploaded into Arduino (could also work with your own)
// There is also the ArduinoSMS class (Arduino quark)
// ====== CONNECTING ======
/*
• Step 1: Load the Standard Firmata firmware to your Arduino board (Standard_Firmata.pde). Note that this seems to currently load best using the Arduino v0016 software (June 2010).
You will have to do the following steps to upload the firmware:
(A): connect the Arduino board to your computer
(B): In the Arduino software:
(a): select the type of board you have from Tools/Board
(b): select the port you plugged the board into, from Tools/Serial Port
(c): open the firmare from File/Sketchbook/Examples/Library-Firmata/StandardFirmata
(d): make sure your firmware runs on the maximum baud rate. The StandardFirmata firmata firmware contains a line around the end of the 'void setup()' loop:
Firmata.begin();
Change it to:
Firmata.begin(115200) // this is probably the default though, at least this is what SCPyduino assumes...
If you are using your own firmware, this would be written as Serial.begin(115200)
(e): press the upload to I/O Board button from the software's interface (shortcut: Cmd-u)
(C): quit Arduino (this is important, as only one software can access the same serial port at a time!)
You can find more info on Firmata here:
"open http://www.firmata.org/wiki/Main_Page".unixCmd
*/
// Now, in SuperCollider:
// • Step 2: Install the SCPyduino SuperCollider quark, if it's not installed yet:
Quarks.install( "SCPyduino", checkoutIfNeeded: false)
// • Step 3: list all devices; this will bring up an array of available serial devices:
SerialPort.listDevices
// or view the array like this:
SerialPort.devices
// • Step 4: Now, from this array, select the index where your Arduino is located (usually that's the usbserial one):
~port = SerialPort.devices[2];
// OR, you could also access the path name directly here, once you know how it is named in your computer. For example, in my MBP machine:
p = SerialPort("/dev/tty.usbserial-A40015nw") // this doesn't seem to always work though...
// If you are NOT using StandardFirmata, but your own firmware, you can configure a number of settings of your SerialPort, upon connecting it to SC:
/* SerialPort instance creation arguments (from the SerialPort helpfile)
// port device path or index
// baudrate baudrate [4800..230400]
// databits 5 | 6 | 7 | 8
// stopbits true | false
// parity nil | 'even' | 'odd'
// crtscts hardware flow control (true | false)
// xonxoff software flow control (true | false)
// exclusive open the device exclusively (true | false)
*/
// For example
(
p = SerialPort(
SerialPort.devices[2], // port name
baudrate: 115200); // maximum baud rate for the Arduino; this will NOT work with StandardFirmata
)
// • Step 5: Create an instance of the SCPyduino object, using the port defined above; then wait for a bit to post the firmata version, which will be returned if the connection is OK:
(
~connectArduino = Routine{
~arduino = SCPyduino.new(~port);
4.wait;
"The Firmata firmware version on this Arduino board is: ".post;
~arduino.firmataVersion.postln}.play;
)
// ====== ACTIVATING AND SETTING PINS: DIGITAL/ANALOG, INPUTS/OUTPUTS ======
// StandardFirmata lets you set from the client application whether your pins will be used as outputs or inputs, and as digital (true/false) or analog (10-bit data). In our case, we do that calling the appropriate pin and method in the SCPyduino class.
// ------ Digital Ports --
// In Firmata, the digital pins are split into digitalPort arrays, with pins [0..7] belonging to .digitalPorts[0] and pins [8..13] belonging to .digitalPorts[1] (only this many are implemented in SCPyduino).
// The appropriate ports have to be set and activated before we can read/write data from/to any pins:
~arduino.digitalPorts[1].active_(1);
// ------ Digital Pins --
// Arduino's digital pins have three possible modes:
/*
digital_output : write data to pin; this mode is enumerated as 0
digital_input : read data from pin; this mode is enumerated as 1
digital_pwm : for PWM output (only on pins: 3, 5, 6, 9, 10, 11 with the standard size Arduino); this mode is enumerated as 2
*/
// ------ Analog ports and Pins --
// Analog pins also need to be activated in order to read incoming values. In the current SCPyduino implementation there is an array of 6 analog ports available, with each index corresponding to the hardware pin of that number:
~arduino.analog[0].active_(1); // activates polling for analog pin 0 on the Arduino microcontroller
// ====== READING DATA (INPUTS) ======
// As with all protocols, we need to sample incoming data at regular intervals. First, we need to tell the microcontroller to poll the activated ports and pins, using the .iterate message. Then, we need a loop to keep sampling the data.
// ------ Digital read --
// activate port 0 (PORTA)
~arduino.digitalPorts[0].active_(1);
// set the mode for digital pin 2 to digital_input
~arduino.digital[2].mode_(~arduino.digital_input);
// read digital pin 2 and post value (true/false):
(
a = fork {
loop {
~arduino.iterate;
~arduino.digital[2].value.postln;
}
};
)
a.stop
// ------ Analog read --
//activate analog pin 0
~arduino.analog[0].active_(1)
//post value of analog pin 0
(
a = fork{
loop{
~arduino.iterate;
~arduino.analog[0].value.postln;
}
};
)
a.stop;
//deactivate reporting analog values
~arduino.analog[0].active(0);
// ====== WRITING DATA (OUTPUTS) ======
// ------ Digital write --
//Activate digital port 1 (PORTB) on the Arduino
~arduino.digitalPorts[1].active_(1)
// Set the mode for digital pin 13 to digital output
~arduino.digital[13].mode_(~arduino.digital_output);
// try it out manually:
~arduino.digital[13].write(1);//turn on LED
~arduino.digital[13].write(0);//turn off LED
//Make the LED blink
(
a = fork{
loop{
~arduino.digital[13].write(1);
0.25.wait;// on for this long
~arduino.digital[13].write(0);
0.25.wait; // off for this long
}
};
)
a.stop; // stop writing data
// ------ PWM write --
// PWM (Pulse Width Modulation) is only possible on pins: 3, 5, 6, 9, 10, 11 with the standard size Arduino.
// Activate digital port 0 (PORTA) on the Arduino
~arduino.digitalPorts[0].active_(1);
// Set the mode for digital pin 3 to pwm output
~arduino.digital[3].mode_(~arduino.digital_pwm);
//pulsating LED
(
var i;
a = fork{
5000.do { |i|
i = (((i % 500) / 500) * 2pi);
~arduino.digital[3].write(sin(i) + 1 * 0.5);
0.005.wait;
}
}
)
a.stop;
// ====== DISCONNECTING ======
// VERY IMPORTANT!: don't forget to close your serial port once you're done (or if you want to access your Arduino board from another software)! Otherwise you may freeze up SC...
~arduino.close
// ====== SOUND EXAMPLE ======
// Here is a very simple sound example, using analog and digital inputs and outputs to control and monitor a synth.
/* What this example uses:
- a switch to turn the synth on and off
- a logarithmic pot to control frequency
- an LED to show if the synth is on or off
- an LED to show how high the frequency is
*/
// a very simple synth:
~synth = CtkSynthDef( \sine, { |outbus = 0, freq = 440, amp = 0.5|
Out.ar(outbus, SinOsc.ar(Lag.kr(freq, 0.05), 0, amp))});
// Connect and activate the Arduino first:
(
~connectArduino = Routine{
// • 1: Connect to the arduino:
~arduino = SCPyduino.new(~port);
4.wait;
"The Firmata firmware version on this Arduino board is: ".post;
~arduino.firmataVersion.postln;
0.25.wait;
// • 2: Activate the ports and pins:
// activate port 0 (PORTA)
~arduino.digitalPorts[0].active_(1);
//Activate digital port 1 (PORTB) on the Arduino
~arduino.digitalPorts[1].active_(1);
//activate analog pin 0
~arduino.analog[0].active_(1);
// set the mode for digital pin 2 to digital_input
~arduino.digital[2].mode_(~arduino.digital_input);
// set the mode for digital pin 13 to digital output
~arduino.digital[13].mode_(~arduino.digital_output);
// Set the mode for digital pin 3 to pwm output
~arduino.digital[3].mode_(~arduino.digital_pwm);
}.play;
)
// now run the loop:
(
~arduinoRWr = fork {
// the ON/OFF switch will be a bit tricky, as we want to only report a changing state
var change, state, switch; // for the toggle
var pot, freq, memArray; // for the pot
change = 0 ! 2; // compare the toggle's state with the previous value
memArray = 0 ! 10; // add a low-pass filtering mechanism for the pot
loop {
// for everything
~arduino.iterate;
// • deal with the ON/OFF switch first, and turn on/off the LED to show the state:
if (~arduino.digital[2].value == false, {state = 0}, {state = 1});
change = change.shift(-1);
change.put((change.size -1), state);
switch = case
{change[0] < change[1]} // means the toggle was turned ON
{
"ON".postln;
if (~note.isPlaying == false, {~note = ~synth.new().play}); // turn the synth on
~arduino.digital[13].write(1); // turn the LED on
}
{change[0] > change[1]} // means the toggle was turned OFF
{
"OFF".postln;
if (~note.isPlaying == true, {~note.free}); // turn the synth off
~arduino.digital[13].write(0); // turn the LED off
};
// • now, the freq pot input, and freq LED output:
// post data before low-pass
"raw data: ".post;
freq = ((~arduino.analog[0].value * 5) + 100).postln;
~arduino.digital[3].write(freq / 6000); // a frequency of about 6000Hz would give out max intensity
// add a low-pass mechanism
memArray = memArray.shift(-1);
memArray.put((memArray.size -1), freq);
freq = memArray.sum / memArray.size;
// post data after the low-pass
"lpf-ed data: ".post;
freq.postln;
~note.freq_(freq);
}
};
)
// stop the loop
~arduinoRWr.stop
// don't forget to free the Serial Port!
~arduino.close